package unm.cs351.p1;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileNameExtensionFilter;
/**
 * Creates the Graphical User Interface for the Photo Editor.
 * This Basic Image Editor contains filters and tools available to the user
 * and are aimed for altering a JPEG image by turning it to grayscale, 
 * appling sepia tones to it, remove red eyes, and chroma key which makes 
 * it possible to layer two image together: the foreground that contains 
 * a "green screen", the background. In addition, the editor has the 
 * ability to open, save, and close JPEG files as well as giving the 
 * user the ability to undo changes.
 * 
 * @author Toby Ngo
 *
 */
public class ImageEditor extends JFrame implements ActionListener{
	/** Stores a default image to display when program runs. */
	private ImageIcon defaultImage;
	
	/** Stores the edited image. */
	private ImageIcon newImage;
	
	/** Stores the image in its original, unmodified state. */
	private BufferedImage originalImage;
	
	private BufferedImage foreground;
	
	/** Stores the image in its "after" state. */
	private BufferedImage editedImage;
	
	/** Image Areas */
	private JLabel originalLabel, editedLabel;
	
	/** Contains the before and after images. */
	private JTabbedPane tabbedPane;

	private JPanel effectsPanel;
	
	/** Toolbar Button */
	private JButton open, save, saveAs, undo;
	
	/** Button toggles on and off effects. */
	private JButton grayButton, sepiaButton, redEyeButton, chromaKeyButton;
			
	/** Displays the currently opened image's dimensions. */
	private JLabel imgDimLabel;
	
	/** Keeps track of how many times we have altered the image. */
	private int numChanges;

	/** Stores BufferedImages as changes are made to the image. */
	private ArrayList<BufferedImage> imgChanges;
		
	/** Keeps track of the current sepia intensity. */
	private int sepiaIntensity;

	/** Selection points */
	private Point pressed, released, prevDraggedPt;
		
	/** Foreground JFrame */
	private JFrame fGFrame;
	
	/** Green Screen color */
	private int selectedColor;
	
	/** Initialized every time changes are made to image. Results added to ArrayList. */
	private BufferedImage tempImg;
	
	/** Allows reference between original image and edited image. Makes it possible to overlap effects */
	private BufferedImage targetImage;
	
	/** Coordinated located on the foreground image that contains the pixel to make invisible. */
	private Dimension bGClickedAt;
	
	/** Increments as undos are made */
	private int undoCounter;

	/** Image path */
	private String filePath;
		
	private Dimension currImgDim, screenDim;
	
	/** Multi-purpose dialog box */
	JFrame dialogFrame = new JFrame();
	
	/** Contains the foreground to be used in Chroma Key */
	JLabel fgLabel;
	
	/** Coordinate keeps track of where the foreground will be laid out */
	Point bgStart, bgEnd;
	
	MouseMotionListener redEyeMouseMotion, backgroundMouseMotion;
	MouseListener redEyeMouseListener, backgroundMouseListener, foregroundMouseListener;
	
	/** GUI setup */
	public ImageEditor(){
		//GUI for Image Area
		super("Toby's Photo Editor");
		setSize(new Dimension(825, 585)); //large enough to display components in JFrame
		setLayout(new FlowLayout(FlowLayout.LEFT));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(getRootPane());
//		setResizable(false);
		
		screenDim = Toolkit.getDefaultToolkit().getScreenSize();
		originalImage = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);		
		foreground = new BufferedImage(640, 480, BufferedImage.TYPE_INT_RGB);
		defaultImage = new ImageIcon(getClass().getResource("Background.jpg"));
		originalLabel = new JLabel(defaultImage);	
		
		imgChanges = new ArrayList<BufferedImage>();
		tabbedPane = new JTabbedPane();
		tabbedPane.addTab("Original Image", originalLabel);
		tabbedPane.setTabPlacement(JTabbedPane.BOTTOM);
		
		//Make sure that after editing an image and there now exists two tabs, disable 
		//all buttons (except the open button) when the original image is selected. Re-enable 
		//all buttons when the edited image is selected.
		tabbedPane.addChangeListener(new ChangeListener(){
			public void stateChanged(ChangeEvent e){
				JTabbedPane pane = (JTabbedPane)e.getSource();
				
				if(pane.getSelectedIndex() == 0 && tabbedPane.getTabCount() == 2){
					disableButtons();
				}else if(pane.getSelectedIndex() == 1){
					enableButtons();
				}
			}		
		});
		
		Dimension toolbarButtonSize = new Dimension(90,27); 
		Dimension effectButtonSize = new Dimension(135, 30);
		
		//Initialization of JButtons
		open = new JButton("Open");
		setButtonProperties(open, toolbarButtonSize);
		save = new JButton("Save");
		setButtonProperties(save, toolbarButtonSize);
		saveAs = new JButton("Save As");
		setButtonProperties(saveAs, toolbarButtonSize);
		undo = new JButton("Undo");
		setButtonProperties(undo, toolbarButtonSize);
		imgDimLabel = new JLabel("");
		
		effectsPanel = new JPanel();
		effectsPanel.setPreferredSize(new Dimension(150, 505));
		grayButton = new JButton("Grayscale");
		setButtonProperties(grayButton, effectButtonSize);
		sepiaButton = new JButton("Sepia");
		setButtonProperties(sepiaButton, effectButtonSize);
		redEyeButton = new JButton("Red Eye Removal");
		setButtonProperties(redEyeButton, effectButtonSize);
		chromaKeyButton = new JButton("Chroma Key");
		setButtonProperties(chromaKeyButton, effectButtonSize);

		dialogFrame.setPreferredSize(new Dimension(200, 100));
		
		effectsPanel.add(grayButton);
		effectsPanel.add(sepiaButton);
		effectsPanel.add(redEyeButton);
		effectsPanel.add(chromaKeyButton);
		
		add(open);
		add(save);
		add(saveAs);
		add(undo);
		add(tabbedPane);
		add(effectsPanel);
		
		disableButtons();
		setVisible(true);	
	}
	
	/**
	 * Disables various buttons on the GUI.
	 */
	public void disableButtons(){
		grayButton.setEnabled(false);
		sepiaButton.setEnabled(false);
		redEyeButton.setEnabled(false);
		chromaKeyButton.setEnabled(false);
		save.setEnabled(false);
		saveAs.setEnabled(false);
		undo.setEnabled(false);
	}
	
	/**
	 * Enables various buttons on the GUI.
	 */
	public void enableButtons(){
		grayButton.setEnabled(true);
		sepiaButton.setEnabled(true);
		redEyeButton.setEnabled(true);
		chromaKeyButton.setEnabled(true);
		save.setEnabled(true);
		saveAs.setEnabled(true);
		undo.setEnabled(true);
	}
	
	/**
	 * Sets the properties of a JButton, including its button size along with the addition of an ActionListener.
	 * @param button - The button to alter.
	 * @param size - The desired button size.
	 */
	public void setButtonProperties(JButton button, Dimension size){
		button.setPreferredSize(size);
		button.addActionListener(this);
	}

	/**
	 * Creates a new tab on the JTabbedPane. This tab will contain an empty BufferedImage to represent the edited image if one doesn't exist.
	 */
	public void newTab(){
		if(editedImage == null){ //If we don't currently have a copy of an image we are editing.
			editedImage = new BufferedImage(currImgDim.width, currImgDim.height, BufferedImage.TYPE_INT_RGB);
			newImage = new ImageIcon(editedImage);
			editedLabel = new JLabel(newImage);
			editedLabel.setIcon(newImage);
		}

		tabbedPane.addTab("Edited Image", editedLabel);
		tabbedPane.setSelectedIndex(1);

	}
	
	/**
	 * Gets the red value from an RGB value, assuming a valid RGB value is passed through this function.
	 * @param rgbVal - An RGB value.
	 * @return A number between 0 and 255.
	 */
	public int getRed(int rgbVal){
		return (rgbVal >> 16) & 0xff;
	}
	
	/**
	 * Gets the green value from an RGB value, assuming a valid RGB value is passed through this function.
	 * @param rgbVal - An RGB value.
	 * @return A number between 0 and 255.
	 */
	public int getGreen(int rgbVal){
		return (rgbVal >> 8) & 0xff;
	}
	
	/**
	 * Gets the blue value from an RGB value, assuming a valid RGB value is passed through this function.
	 * @param rgbVal - An RGB value.
	 * @return A number between 0 and 255.
	 */
	public int getBlue(int rgbVal){
		return rgbVal & 0xff;
	}
	
	/**
	 * Gets the alpha value from an RGB value, assuming a valid RGB value is passed through this function.
	 * @param rgbVal - An RGB value.
	 * @return A number between 0 and 255.
	 */
	public int getAlpha(int rgbVal){
		return (rgbVal >> 24) & 0xff;
	}
	
	/**
	 * Converts an RGB value to its corresponding YUV value.
	 * @param rgbVal - RGB value
	 * @return YUV value
	 */
	public int rgbToYUV(int rgbVal){
		int redVal = getRed(rgbVal);
		int greenVal = getGreen(rgbVal);
		int blueVal = getBlue(rgbVal);
		
		int y = (int) (0.299 * redVal + 0.587 * greenVal + 0.114 * blueVal);
		int u = (int) (-0.14713 * redVal - 0.28886 * greenVal + 0.436 * blueVal);
		int v = (int)(0.615 * redVal - 0.51499 * greenVal - 0.10001 * blueVal);
		
		return  y << 16 | u << 8 | v;
	}
	
	/**
	 * Overwrites the image with the edited image without notifying the user.
	 * @throws IOException - Unable to write to file.
	 */
	public void save() throws IOException{
			File file = new File(filePath);
			ImageIO.write(editedImage, "JPEG", file);
	}
	
	public void saveAs() throws IOException{
		JFileChooser fileChooser = new JFileChooser();
	    FileNameExtensionFilter filter = new FileNameExtensionFilter(".jpg file", "jpg");
	    fileChooser.addChoosableFileFilter(filter);
		int selection = fileChooser.showSaveDialog(this);
		if(selection == JFileChooser.CANCEL_OPTION)
			return;
		File file = fileChooser.getSelectedFile();
		if(file == null)
			return;
		String savedPath = file.getPath();
		if(!(savedPath.toLowerCase().endsWith(".jpg")) || !(savedPath.toLowerCase().endsWith(".jpeg"))){
			File newFile = new File(savedPath + ".jpg");
			ImageIO.write(editedImage, "JPG", newFile);
		}else{
		 	ImageIO.write(editedImage, "JPG", file);
		}
	}
	
	/**
	 * Populates an ArrayList with images that will represent the changes made to the image while . 
	 * This will allow the user to undo changes by accessing the List.
	 * 
	 * @param currentState - Image to be added to the undo ArrayList.
	 */
	public void makeUndoable(BufferedImage currentState){
		if(imgChanges.size() <= numChanges){ //if the current image state resides in the middle of the array list
			imgChanges.add(currentState);			
		}else{
			imgChanges.set(numChanges, currentState); //the current image state will be set at the end of the ArrayList.
		}
		
		undoCounter = 0;
		numChanges ++;
	}
	
	/**
	 * Converts a BufferedImage into Grayscale. 
	 * @param image - The image we wish to filter to grayscale.
	 */
	public void grayscale(BufferedImage image){
		
		duplicateImage();
		
		for(int i = 0; i<currImgDim.width; i++){
			for(int j = 0; j<currImgDim.height; j++){
				int origPix = targetImage.getRGB(i, j);
				int grayPix = (getRed(origPix) + getGreen(origPix) + getBlue(origPix))/3;
				int newRGB = grayPix<<16 | grayPix<<8 | grayPix;
				image.setRGB(i, j, newRGB);
				tempImg.setRGB(i, j, newRGB);
			}
		}
		
		makeUndoable(tempImg);
	}
	
	/**
	 * Converts a BufferedImage into Sepia Tones.
	 * @param image - The image we wish to apply the effect to.
	 * @param intensity - The intensity of the filter.
	 */
	public void sepia(BufferedImage image, int intensity){				
		sepiaIntensity = intensity;
		
		duplicateImage();
		
		for(int i = 0; i<currImgDim.width; i++){
			for(int j = 0; j<currImgDim.height; j++){
				int origPix = targetImage.getRGB(i, j);
				int grayPix = (getRed(origPix) + getGreen(origPix) + getBlue(origPix))/3;
				int sepiaRed = (grayPix + intensity * 2);
				if(sepiaRed > 255)  //since we're always brightening red, there's a chance the red value will go out of bounds.
					sepiaRed = 255;
				
				int sepiaGreen = (grayPix + intensity);
				if(sepiaGreen > 255)  //since we're always brightening green, there's a chance the green value will go out of bounds.
					sepiaGreen = 255;

				int sepiaBlue = grayPix - intensity;
				if(sepiaBlue < 0) //since we're always darkening blue, there's a chance the blue value will go out of bounds.
					sepiaBlue = 0;
				
				int newRGB = sepiaRed<<16 | sepiaGreen<<8 | sepiaBlue;
				image.setRGB(i, j, newRGB);
				tempImg.setRGB(i,j, newRGB);
			}
		}
		makeUndoable(tempImg);
	}
	
	/**
	 * Decreases the red value in a pixel.
	 * @param x - coordinate of the image
	 * @param y - coordinate of the image
	 * @param rgb - pixel to change
	 */
	public void removeRed(int x, int y, int rgb){
		editedImage.setRGB(x,y, (getGreen(rgb) + getBlue(rgb))/2 <<16 | getGreen(rgb)<<8 | getBlue(rgb));
		tempImg.setRGB(x,y, (getGreen(rgb) + getBlue(rgb))/2 <<16 | getGreen(rgb)<<8 | getBlue(rgb));
	}

	/**
	 * Creates a new Bufferedimage ready for editing. This image will be stored in both
	 * editedImage and tempImg.
	 */
	public void duplicateImage(){
		tempImg = new BufferedImage(currImgDim.width, currImgDim.height, BufferedImage.TYPE_INT_RGB);;

		if(numChanges == 0){ //only original image is present 
			targetImage = originalImage; //reference the original image
		}else{
			targetImage = editedImage; //reference the edited image
		}
		
		//copy original image to new tab
		for(int i = 0; i<currImgDim.width; i++){
			for(int j = 0; j<currImgDim.height; j++){
				int origPix = targetImage.getRGB(i, j);
				editedImage.setRGB(i, j, origPix);
				tempImg.setRGB(i, j, origPix);
			}
		}
	}
	
	/**
	 * Inverts the RGB values in a given rectangle of the editedImage.
	 * 
	 * @param startX - X coordinate for the starting index
	 * @param startY - Y coordinate for the starting index
	 * @param endX - X coordinate for the starting index
	 * @param endY - Y coordinate for the ending index
	 */
	public void invertRect(int startX, int startY, int endX, int endY){
		for(int i = startX; i<endX; i++){
			for(int j = startY; j<endY; j++){
				int rgb = editedImage.getRGB(i,j);
				editedImage.setRGB(i,j, (255 - getRed(rgb))<<16 | (255 - getGreen(rgb))<<8 | (255 - getBlue(rgb)));
			}
		}
	}
	
	/**
	 * Checks to see if the mouse has reached outside the image through MouseEvents. 
	 * If so, correct the point by setting the desired point to the edge of the image.
	 */
	public void setToEdge(Point p){
		if(p.x > currImgDim.width)
			p.x = currImgDim.width;
		if(p.x < 0)
			p.x = 0;
		if(p.y > currImgDim.height)
			p.y = currImgDim.height;
		if(p.y < 0)
			p.y = 0;
	}
	
	/**
	 * Allows the user to 'draw' a selection box (in any direction) over the red eye to decrease the red on the eye.
	 */
	public void redEyeRemoval(){

		if(tabbedPane.getSelectedIndex() == 0){
			newTab();
			duplicateImage();
		}

		redEyeMouseMotion = new MouseAdapter(){

			public void mouseDragged(MouseEvent e){
				//Figure out which direction the user is dragging the selection box.
				if(e.getX() >= pressed.x){ //The user selected downwards.	
					if(e.getY() >= pressed.y){ //The user selected from the left to right.	
						Point currEvent = new Point(e.getX(), e.getY());
						setToEdge(currEvent);
						if(prevDraggedPt != null){
//							invertRect(pressed.x, pressed.y, prevDraggedPt.x, prevDraggedPt.y);
						}
//						invertRect(pressed.x, pressed.y, currEvent.x, currEvent.y);

						prevDraggedPt = new Point(currEvent.x, currEvent.y);

					}
				}

				editedLabel.repaint();
			}	
		};
		
		redEyeMouseListener = new MouseAdapter(){
	
			public void mousePressed(MouseEvent e) {
				prevDraggedPt = null;
				pressed = new Point(e.getX(), e.getY());
				
			}
			public void mouseReleased(MouseEvent e) {
				duplicateImage();

				released = new Point(e.getX(), e.getY());
				setToEdge(released);
//				invertRect(pressed.x, pressed.y, released.x, released.y);

				//Figure out which direction the user is dragging the selection box.
				if(released.y > pressed.y){ //The user selected downwards.
					
					if(released.x > pressed.x){ //The user selected from the left to right.
						//modify pixels that have been highlighted by the user
						for(int x = pressed.x;  x < released.x; x++){
							for(int y = pressed.y; y < released.y; y++){
								int origPix = targetImage.getRGB(x, y);
								if(getRed(origPix) > (getGreen(origPix) + getBlue(origPix))){
									removeRed(x,y, origPix);
								}else{
									editedImage.setRGB(x,y, origPix);
									tempImg.setRGB(x,y, origPix);

								}
							}
						}
					}else if(released.x < pressed.x){ //The user selected from right to left.
						for(int x = released.x;  x < pressed.x; x++){
							for(int y = pressed.y; y < released.y; y++){
								int origPix = targetImage.getRGB(x, y);
								if(getRed(origPix) > (getGreen(origPix) + getBlue(origPix))){
									removeRed(x,y, origPix);
								}else{
									editedImage.setRGB(x,y, origPix);
									tempImg.setRGB(x,y, origPix);

								}
							}
						}
					}
				}else if(pressed.y > released.y){ //The user selected upwards.
					if(pressed.x > released.x){ //The user selected from right to left.
						for(int x = released.x;  x < pressed.x; x++){
							for(int y = released.y; y < pressed.y; y++){
								int origPix = targetImage.getRGB(x, y);
								if(getRed(origPix) > (getGreen(origPix) + getBlue(origPix))){
									removeRed(x,y, origPix);
								}else{
									editedImage.setRGB(x,y, origPix);
									tempImg.setRGB(x,y, origPix);
								}
							}
						}
					}else if(pressed.x < released.x){ //The user is selecting from left to right.
						for(int x = pressed.x;  x < released.x; x++){
							for(int y = released.y; y < pressed.y; y++){
								int origPix = targetImage.getRGB(x, y);
								if(getRed(origPix) > (getGreen(origPix) + getBlue(origPix))){
									removeRed(x,y, origPix);
								}else{
									editedImage.setRGB(x,y, origPix);
									tempImg.setRGB(x,y, origPix);

								}
							}
						}
					}
				}
				editedLabel.repaint();
				makeUndoable(tempImg);

			}
		
		};
		
		editedLabel.addMouseListener(redEyeMouseListener);
		originalLabel.addMouseListener(redEyeMouseListener);
		editedLabel.addMouseMotionListener(redEyeMouseMotion);
	}

   /**
	 * Assuming the user initially opened the background image, this method asks the user to load in the foreground image and select the color to remove.
	 */
	public void chromaKey(){
		JFileChooser fileChooser = new JFileChooser();
		//Only allow user to select .jpg and .jpeg files.
	    FileNameExtensionFilter filter = new FileNameExtensionFilter(".jpg and .jpeg files", "jpg", "jpeg");
	    fileChooser.addChoosableFileFilter(filter);
		fileChooser.setCurrentDirectory(new File("."));
		fileChooser.showOpenDialog(null);
		File file = fileChooser.getSelectedFile();

		try{
			if(file != null){
				foreground = ImageIO.read(new File(file.getAbsolutePath()));
			}
		} catch (IOException e1){
			e1.printStackTrace();
		}
		
		if((foreground.getWidth() > currImgDim.width) && (foreground.getHeight() > currImgDim.height)
				|| (foreground.getWidth() > currImgDim.width)
				|| (foreground.getHeight() > currImgDim.height)){
			JOptionPane.showMessageDialog(dialogFrame, "This image is too large to fit the background. Please use another image.");
			return;
		}
		
		ImageIcon fGImageIcon = new ImageIcon(foreground);
		fgLabel = new JLabel(fGImageIcon);	
		JScrollPane fGPane = new JScrollPane(fgLabel);
		fGFrame = new JFrame();
		int fGWidth = foreground.getWidth();
		int fGHeight = foreground.getHeight();
		
		//Resize the panel that displays the greenscreen image based on whether or not the image is too big to fit the screen.
		if(fGWidth > screenDim.width || fGHeight > screenDim.height){
			if(fGWidth > screenDim.width && screenDim.height > fGHeight){ //image is too wide to fit the screen
				fGFrame.setSize((int)(0.75 * screenDim.width), fGHeight + 50);
			}else if(screenDim.width > fGWidth && fGHeight > screenDim.height){ //image is too tall to fit the screen
				fGFrame.setSize(fGWidth + 50, (int)(0.75 * screenDim.height));
			}else{
				fGFrame.setSize((int)(0.75 * screenDim.width), (int)(0.75 * screenDim.height)); //image is too wide and too tall to fit the screen
			}
		}else{
			fGFrame.setSize(fGWidth + 50, fGHeight + 50);
		}
		
		fGFrame.add(fGPane);
		fGFrame.setTitle("Green Screen");
		fGFrame.setLocationRelativeTo(getRootPane());
		fGFrame.setVisible(true);
		
		backgroundMouseListener = new MouseAdapter(){
			public void mousePressed(MouseEvent e){
				bGClickedAt = new Dimension(e.getX(), e.getY());
				if(tabbedPane.getSelectedIndex() == 0){
					newTab();
					duplicateImage();
				}
				
				duplicateImage();
				
				pressed = new Point(e.getX(), e.getY());

				//iterate through the foreground and set the corresponding pixel in background to all pixels other than the RGB value selected by the user.
				for(int i = 0; i < foreground.getWidth(); i++){
					for(int j = 0; j < foreground.getHeight(); j++){
						int fgRGB = foreground.getRGB(i, j); //RGB value for the current pixel
						int fgYUV = rgbToYUV(fgRGB); //YUV value for the current pixel
						
						//Euclidean Distance between the selected RGB value and the current pixel ((r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2)^(1/2)
						double eDist = Math.sqrt(Math.pow((getRed(fgYUV) - getRed(selectedColor)),2) + Math.pow((getGreen(fgYUV) - getGreen(selectedColor)),2) + Math.pow((getBlue(fgYUV) - getBlue(selectedColor)),2));
						//find distance in 3D space between the FG and BG pixels. if they're close, don't add pixel to BG
						if(eDist < Main.CHROMAKEY_THRESHOLD){
							//do nothing
						}else{
							//Force placement of foreground image depending on the location the user chooses to place the image.
							
							//x and y position exceeds index of background. relocate to bottom right hand of background
							if((e.getX() + foreground.getWidth() > currImgDim.width) && (e.getY() + foreground.getHeight() > currImgDim.height)){
								editedImage.setRGB(currImgDim.width - foreground.getWidth() + i, currImgDim.height - foreground.getHeight() + j, fgRGB);
								tempImg.setRGB(currImgDim.width - foreground.getWidth() + i, currImgDim.height - foreground.getHeight() + j, fgRGB);
								
								bgStart = new Point(currImgDim.width - foreground.getWidth(), currImgDim.height - foreground.getHeight());
								bgEnd = new Point(currImgDim.width, currImgDim.height);
								
							}else if(e.getX() + foreground.getWidth() > currImgDim.width){ //x position exceeds index of background.
								editedImage.setRGB(currImgDim.width - foreground.getWidth()-1 + i, e.getY() + j, fgRGB);
								tempImg.setRGB(currImgDim.width - foreground.getWidth()-1 + i, e.getY() + j, fgRGB);
								
								bgStart = new Point(currImgDim.width - foreground.getWidth()-1, e.getY());
								bgEnd = new Point(currImgDim.width, e.getY() + foreground.getHeight());
								
							}else if(e.getY() + foreground.getHeight() > currImgDim.height){ //y position exceeds index of background.
								editedImage.setRGB(e.getX() + i, currImgDim.height - foreground.getHeight() + j, fgRGB);
								tempImg.setRGB(e.getX() + i, currImgDim.height - foreground.getHeight() + j, fgRGB);
								
								bgStart = new Point(e.getX(), currImgDim.height - foreground.getHeight());
								bgEnd = new Point(e.getX() + foreground.getWidth(), currImgDim.height);
								
							}else{ //valid placement
								editedImage.setRGB(e.getX() + i, e.getY() + j, fgRGB);
								tempImg.setRGB(e.getX() + i, e.getY() + j, fgRGB);
								
								bgStart = new Point(e.getX(), e.getY());
								bgEnd = new Point(e.getX() + foreground.getWidth(), e.getY() + foreground.getHeight());
							}
						}				
					}
				}
				makeUndoable(tempImg);

//				invertRect(bgStart.x, bgStart.y, bgEnd.x, bgEnd.y);
				editedLabel.repaint();			
			}
			
			public void mouseReleased(MouseEvent e){
//				invertRect(bgStart.x, bgStart.y, bgEnd.x, bgEnd.y);
				editedLabel.repaint();

			}
			
			public void mouseClicked(MouseEvent e){
				originalLabel.removeMouseListener(backgroundMouseListener);
				editedLabel.removeMouseListener(backgroundMouseListener);
			}
		};
		
		foregroundMouseListener = new MouseAdapter(){
			public void mousePressed(MouseEvent e) {
				selectedColor = rgbToYUV(foreground.getRGB(e.getX(), e.getY()));			
				fGFrame.setVisible(false);
				JOptionPane.showMessageDialog(dialogFrame, 
						"      Please click on the upper left location of where you want the foreground to start at.\n" +
						"Location will automatically readjust if you click too far to the right or bottom of the background");
			}
			
		};

		JOptionPane.showMessageDialog(dialogFrame, "Please choose the color you wish to remove.");

		originalLabel.addMouseListener(backgroundMouseListener);
		editedLabel.addMouseListener(backgroundMouseListener);
		fgLabel.addMouseListener(foregroundMouseListener);
	}

	/**
	 * Invoked when an action within the GUI occurs.
	 */
	@Override
	public void actionPerformed(ActionEvent e){
		if(e.getSource() == open){
			if(tabbedPane.getTabCount() > 1){
				JOptionPane.showMessageDialog(dialogFrame, "Note: Unsaved data will be lost if you proceed to open a new image.");
			}
			JFileChooser fileChooser = new JFileChooser();
			
			//Only allow user to select .jpg and .jpeg files.
		    FileNameExtensionFilter filter = new FileNameExtensionFilter(".jpg and .jpeg files", "jpg", "jpeg");
		    fileChooser.addChoosableFileFilter(filter);
			fileChooser.setCurrentDirectory(new File("."));
			fileChooser.showOpenDialog(null);
			File file = fileChooser.getSelectedFile();
	
			if(file != null){
				try {
					originalImage = ImageIO.read(new File(file.getAbsolutePath()));
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}else
				return;
			
			//Remove the edited image from the tabbed pane if it exists.
			if(tabbedPane.getTabCount() > 1 ){
				tabbedPane.remove(editedLabel);
			}
			
			editedImage = null;
			sepiaIntensity = 0;
			imgChanges.clear();
			
			open.setEnabled(true);
			saveAs.setEnabled(true);
			save.setEnabled(false);
			undo.setEnabled(false);
			grayButton.setEnabled(true);
			sepiaButton.setEnabled(true);
			redEyeButton.setEnabled(true);
			redEyeButton.setText("Red Eye Removal");
			chromaKeyButton.setEnabled(true);
			
			numChanges = 0;
			undo.setEnabled(false);
			ImageIcon newImage = new ImageIcon(originalImage);
			setSize(new Dimension(newImage.getIconWidth() + 185, newImage.getIconHeight()+105));
			effectsPanel.setPreferredSize(new Dimension(150, newImage.getIconHeight()));
			currImgDim = new Dimension();
			currImgDim.width = newImage.getIconWidth();
			currImgDim.height = newImage.getIconHeight();
			setTitle(file + " (" + currImgDim.width + " x " + currImgDim.height + " pixels)");
			newImage.getImage().flush();

			originalLabel.setIcon(newImage);
			imgDimLabel.setText(newImage.getIconWidth() + " x " + newImage.getIconHeight() + " pixels");

			setLocationRelativeTo(getRootPane());
			
			if(currImgDim.width < 500 && currImgDim.height < 500){
				setSize(new Dimension(currImgDim.width + 230, currImgDim.height + 121));
				
			}
		}else if(e.getSource() == save){
			try {
				save();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}else if(e.getSource() == saveAs){
			try {
				saveAs();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}else if(e.getSource() == undo){
				if(numChanges-2 < 0){
					undo.setEnabled(false);
					tabbedPane.removeTabAt(1);
				}else{
					editedImage = imgChanges.get(numChanges-2);
					tabbedPane.removeTabAt(1);
					
					newImage = new ImageIcon(editedImage);
					editedLabel = new JLabel(newImage);
					editedLabel.setIcon(newImage);
					
					tabbedPane.addTab("Edited Image", editedLabel);
					editedLabel.repaint();
					tabbedPane.setSelectedIndex(1);
				}
			
			numChanges --;
			undoCounter ++;
			
			if((undoCounter+1 > Main.MAX_NUMBER_OF_UNDOS) || numChanges == 0){
				undo.setEnabled(false);
			}else{
				undo.setEnabled(true);
			}
		}else if(e.getSource() == grayButton){
			
			if(tabbedPane.getSelectedIndex() == 0){ //only original image is present
				newTab();
			}else{ //remove tab that contains edited image and re-apply the effect
				editedLabel.repaint();
			}
			grayscale(editedImage);
			grayButton.setEnabled(false);
		
		}else if(e.getSource() == sepiaButton){
			if(tabbedPane.getSelectedIndex() == 0){ //only original image is present
				newTab();
				sepia(editedImage, Main.SEPIA_INTENSITY);
			}else{//remove tab that contains edited image and re-apply the effect
				sepia(editedImage, sepiaIntensity += 5);
				editedLabel.repaint();
			}
			grayButton.setEnabled(true);

		}else if(e.getSource() == redEyeButton){
			JOptionPane.showMessageDialog(dialogFrame, "Please highlight diagonally through the affected eyes.");
			redEyeRemoval();

		}else if(e.getSource() == chromaKeyButton){
			JOptionPane.showMessageDialog(dialogFrame, 
					"Please make sure you have the background image currently opened.\n" +
					"           Proceeding to browse for Foreground Image.");

			if(tabbedPane.getTabCount() == 1){ //only original image is present
				newTab();
				
				//Will be added to the imgChanges ArrayList. This will make it possible for us to undo changes.
				tempImg = new BufferedImage(currImgDim.width, currImgDim.height, BufferedImage.TYPE_INT_RGB);;
				
				//copy original image to new tab
				for(int i = 0; i<currImgDim.width; i++){
					for(int j = 0; j<currImgDim.height; j++){
						int origPix = originalImage.getRGB(i, j);
						editedImage.setRGB(i, j, origPix);
						tempImg.setRGB(i, j, origPix);
					}
				}
				chromaKey();

			}else{
				chromaKey();
			}
		}
	}
}

